#include "nighthawk/user_defined_output/user_defined_output_plugin.h"

#include "external/envoy/source/common/config/utility.h"

#include "source/user_defined_output/log_response_headers_plugin.h"
#include "source/user_defined_output/user_defined_output_plugin_creator.h"

#include "test/user_defined_output/fake_plugin/fake_user_defined_output.h"
#include "test/user_defined_output/fake_plugin/fake_user_defined_output.pb.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace Nighthawk {
namespace {

using ::envoy::config::core::v3::TypedExtensionConfig;
using ::Envoy::Protobuf::TextFormat;
using ::nighthawk::FakeUserDefinedOutputConfig;
using ::nighthawk::LogResponseHeadersConfig;
using ::testing::HasSubstr;

UserDefinedOutputConfigFactoryPair
CreateFactoryConfigPair(const std::string& plugin_name,
                        const Envoy::Protobuf::Message& plugin_config) {
  TypedExtensionConfig typed_config;
  *typed_config.mutable_name() = plugin_name;
  typed_config.mutable_typed_config()->PackFrom(plugin_config);

  auto* factory = Envoy::Config::Utility::getAndCheckFactory<UserDefinedOutputPluginFactory>(
      typed_config, false);

  return {typed_config, factory};
}

TEST(CreateUserDefinedOutputPlugins, ReturnsEmptyVectorWhenNoConfigs) {
  std::vector<UserDefinedOutputConfigFactoryPair> config_factory_pairs{};
  std::vector<UserDefinedOutputNamePluginPair> plugins{};
  absl::StatusOr<std::vector<UserDefinedOutputNamePluginPair>> user_defined_output_plugin_pairs =
      createUserDefinedOutputPlugins(config_factory_pairs, 0);
  ASSERT_TRUE(user_defined_output_plugin_pairs.ok());
  EXPECT_EQ(*user_defined_output_plugin_pairs, plugins);
}

TEST(CreateUserDefinedOutputPlugins, CreatesPluginsForEachConfig) {
  std::vector<UserDefinedOutputConfigFactoryPair> config_factory_pairs{};
  FakeUserDefinedOutputConfig fake_config;
  TextFormat::ParseFromString("fail_per_worker_output: false", &fake_config);
  config_factory_pairs.push_back(
      CreateFactoryConfigPair("nighthawk.fake_user_defined_output", fake_config));
  LogResponseHeadersConfig logging_config;
  TextFormat::ParseFromString("logging_mode:LM_LOG_ALL_RESPONSES", &logging_config);
  config_factory_pairs.push_back(
      CreateFactoryConfigPair("nighthawk.log_response_headers_plugin", logging_config));

  absl::StatusOr<std::vector<UserDefinedOutputNamePluginPair>> plugins =
      createUserDefinedOutputPlugins(config_factory_pairs, 0);
  ASSERT_TRUE(plugins.ok());
  EXPECT_EQ(plugins->size(), 2);
  EXPECT_EQ((*plugins)[0].first, "nighthawk.fake_user_defined_output");
  EXPECT_NE(dynamic_cast<FakeUserDefinedOutputPlugin*>((*plugins)[0].second.get()), nullptr);
  EXPECT_EQ((*plugins)[1].first, "nighthawk.log_response_headers_plugin");
  EXPECT_NE(dynamic_cast<LogResponseHeadersPlugin*>((*plugins)[1].second.get()), nullptr);
}

TEST(CreateUserDefinedOutputPlugins, PropagatesCreationFailures) {
  std::vector<UserDefinedOutputConfigFactoryPair> config_factory_pairs{};
  LogResponseHeadersConfig invalid_logging_config;
  TextFormat::ParseFromString("", &invalid_logging_config);
  config_factory_pairs.push_back(
      CreateFactoryConfigPair("nighthawk.log_response_headers_plugin", invalid_logging_config));

  absl::StatusOr<std::vector<UserDefinedOutputNamePluginPair>> plugins =
      createUserDefinedOutputPlugins(config_factory_pairs, 0);
  ASSERT_FALSE(plugins.ok());
  ASSERT_EQ(plugins.status().message(),
            "Invalid configuration for LogResponseHeadersPlugin. Must provide a valid LoggingMode");
}

} // namespace
} // namespace Nighthawk
